/*
 Xern Entity Relocation Network
 Copyright (C) 2011 Ronie Salgado <roniesalg@gmail.com>

 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
 in the Software without restriction, including without limitation the rights
 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 copies of the Software, and to permit persons to whom the Software is
 furnished to do so, subject to the following conditions:

 The above copyright notice and this permission notice shall be included in
 all copies or substantial portions of the Software.

 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 THE SOFTWARE.
*/

#include "Xern/DataTypes.hpp"
#include "Xern/DataFilter.hpp"
#include "Xern/Exceptions.hpp"

namespace Xern
{
	// File filter
	FileFilter::FileFilter(FILE *file, bool own)
		: file(file), own(own)
	{
		assert(file);
	}

	FileFilter::~FileFilter()
	{
		if(file && own)
			fclose(file);
	}

	size_t FileFilter::Read(size_t size, void *buffer)
	{
		return fread(buffer, size, 1, file)*size;
	}

	size_t FileFilter::Write(size_t size, const void *data)
	{
		return fwrite(data, size, 1, file)*size;
	}

	size_t FileFilter::Skip(size_t amount)
	{
		size_t old = ftell(file);
		fseek(file, amount, SEEK_CUR);
		return ftell(file) - old;;
	}

	bool FileFilter::Seek(size_t offset, SeekType origin)
	{
		int type;
		switch(origin)
		{
		case ST_SET:
			type = SEEK_SET;
			break;
		case ST_CUR:
			type = SEEK_CUR;
			break;
		case ST_END:
			type = SEEK_END;
			break;
		default:
			throw std::logic_error("Invalid argument");
		}

		return fseek(file, offset, origin) == 0;
	}

	bool FileFilter::Flush()
	{
		fflush(file);
		return true;
	}

	size_t FileFilter::GetPosition() const
	{
		return ftell(file);
	}

	// Buffer filter
	BufferFilter::BufferFilter(size_t len, void *buffer)
		: len(len), buffer(buffer), position(0)
	{
		own = (buffer == NULL);
		if(own)
			this->buffer = new char[len];

		assert(this->buffer);
	}

	BufferFilter::~BufferFilter()
	{
		if(own)
			delete [] (char*)buffer;
	}

	size_t BufferFilter::Read(size_t size, void *buffer)
	{
		// Cast the buffers.
		const u8 *src = static_cast<u8*> (this->buffer);
		u8 *dst = static_cast<u8*> (buffer);

		// Check the size.
		size_t toread = size;
		if(position + size > len)
			toread = len - position;

		if(toread > 0)
		{
			for(size_t i = 0; i < toread; i++)
				dst[i] = src[i + position];
			position += toread;
			return toread;
		}
		else
		{
			// Overflow.
			return 0;
		}
	}

	size_t BufferFilter::Write(size_t size, const void *data)
	{
		// Cast the buffers.
		const u8 *src = static_cast<const u8*> (data);
		u8 *dst = static_cast<u8*> (this->buffer);

		// Check the size.
		if(position + size <= len)
		{
			for(size_t i = 0; i < size; i++)
				dst[i + position] = src[i];
			position += size;
			return size;
		}
		else
		{
			// Overflow.
			// Shouldn't reach here, at least in debug mode.
			assert(false);
			return 0;
		}
	}

	size_t BufferFilter::Skip(size_t amount)
	{
		if(position + amount < len)
		{
			position += amount;
			return amount;
		}
		else
		{
			return 0;
		}
	}

	bool BufferFilter::Seek(size_t offset, SeekType origin)
	{
		switch(origin)
		{
		case ST_SET:
			if(offset > len)
				return false;
			position = offset;
			return true;
		case ST_CUR:
			return Skip(offset) == offset;
		case ST_END:
			if(offset == 0)
			{
				position = len;
				return true;
			}
			else
			{
				return false;
			}
		default:
			throw std::logic_error("Invalid argument");
		}
	}

	bool BufferFilter::Flush()
	{
		return true;
	}

	size_t BufferFilter::GetPosition() const
	{
		return position;
	}

	// Zero-coding filter
	ZeroCodeFilter::ZeroCodeFilter(boost::shared_ptr<IDataFilter> parent)
		: parent(parent)
	{
		nextZeros = 0;
	}

	ZeroCodeFilter::~ZeroCodeFilter()
	{
	}

	size_t ZeroCodeFilter::Read(size_t size, void *buffer)
	{
		u8 *dst = (u8*)buffer;
		u8 src;

		size_t amount = size;
		for(; size > 0; size--)
		{
			if(nextZeros > 0)
			{
				*dst++ = 0;
				nextZeros--;
			}
			else
			{
				// Read the next byte.
				if(!parent->Read(1, &src))
					return 0;

				// Check for zeros.
				if(src == 0)
				{
					// Read the zero count.
					if(!parent->Read(1, &src))
						return 0;

					nextZeros = src;
					// Decrease the zeros.
					if(nextZeros) // Avoid overflow.
						nextZeros--;

					// Write a zero.
					*dst++ = 0;
				}
				else
				{
					// Write the readed byte.
					*dst++ = src;
				}
			}
		}

		return amount;
	}

	size_t ZeroCodeFilter::Write(size_t size, const void *data)
	{
		u8 *src= (u8*)data;
		u8 dst;

		size_t amount = size;
		for(; size > 0; size--)
		{
			if(*src == 0)
			{
				nextZeros++;
				if(nextZeros == 0xFF)
					Flush();
			}
			else
			{
				if(nextZeros > 0)
					Flush();

				dst = *src;
				if(!parent->Write(1, &dst))
					return 0;
			}

			// Increase the source pointer.
			src++;
		}

		return amount;
	}

	size_t ZeroCodeFilter::Skip(size_t amount)
	{
		u8 src;

		size_t skipped = amount;
		while(amount > 0)
		{
			if(nextZeros > 0)
			{
				if(amount >= nextZeros)
				{
					amount -= nextZeros;
					nextZeros = 0;
				}
				else // amount < nextZeros
				{
					nextZeros -= amount;
					return skipped;
				}
			}
			else
			{
				// Read the next byte.
				if(!parent->Read(1, &src))
					return 0;

				// Check for zeros.
				if(src == 0)
				{
					// Read the zero count.
					if(!parent->Read(1, &src))
						return 0;

					nextZeros = src;
				}
				else
				{
					// Decrease the amount.
					amount--;
				}
			}
		}

		return skipped;
	}

	bool ZeroCodeFilter::Seek(size_t offset, SeekType origin)
	{
		return false;
	}

	bool ZeroCodeFilter::Flush()
	{
		if(nextZeros > 0)
		{
			// Write a zero.
			u8 byte = 0;
			if(!parent->Write(1, &byte))
				return false;

			// Write the number of repetitions.
			byte = nextZeros;
			if(!parent->Write(1, &byte))
				return false;

			// Reset the number of zeros.
			nextZeros = 0;
		}

		return true;
	}

	size_t ZeroCodeFilter::GetPosition() const
	{
		return parent->GetPosition();
	}
}; // namespace Xern
